﻿using System;
using System.Diagnostics;

namespace Ns.Threading
{
    /// <summary>
    /// Scopes a type instance.
    /// </summary>
    /// <typeparam name="T">Any type.</typeparam>
    /// <seealso href="http://msdn.microsoft.com/en-us/magazine/cc300805.aspx">Scope&lt;T&gt; and More</seealso>
    public class Scope<T> : IDisposable
    {
        private bool _disposed;
        private bool _disposeInstance;
        private T _instance;        
        private Scope<T> _parent;        
        [ThreadStatic]
        private static Scope<T> _head;

        /// <summary>
        /// Initializes a new instance of the <see cref="Scope&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="instance">The instance.</param>
        public Scope(T instance) : this(instance, true) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Scope&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="disposeInstance">Indicates if the scoped <paramref name="instance"/> is disposed when its scope is disposed.</param>        
        public Scope(T instance, bool disposeInstance)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");

            _instance = instance;
            _disposeInstance = disposeInstance;

            _parent = _head;
            _head = this;
        }

        /// <summary>
        /// Gets the current instance of T in the ambient scope.
        /// </summary>
        /// <value>The current instance of T.</value>
        public static T Current
        {
            get { return _head != null ? _head._instance : default(T); }
        }

        /// <summary>
        /// Disposes the ambient scope and its current instance when applicable.
        /// </summary>
        public void Dispose()
        {
            if (!_disposed)
            {
                _disposed = true;

                Debug.Assert(this == _head, "Disposed out of order.");
                _head = _parent;

                if (_disposeInstance)
                {
                    var disposable = _instance as IDisposable;
                    if (disposable != null) 
                        disposable.Dispose();
                }
            }
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="Scope&lt;T&gt;"/> class from being created.
        /// </summary>
        protected Scope()
        {
        }
    }
}